package org.lgiavedoni.android.franq.core.communication.imp.jaber;

import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.lgiavedoni.android.franq.core.communication.CommunicationSource;
import org.lgiavedoni.android.franq.core.envent.Event;
import org.lgiavedoni.android.franq.core.envent.listener.EventListener;
import org.lgiavedoni.android.franq.core.message.MessageIn;
import org.lgiavedoni.android.franq.core.message.MessageOut;
import org.lgiavedoni.android.franq.core.service.Service;
import org.lgiavedoni.android.franq.event.internal.ConfigurationChangeEvent;
import org.lgiavedoni.android.franq.service.communication.CommunicationService;
import org.lgiavedoni.android.franq.service.config.ConfigService;
import org.lgiavedoni.android.franq.service.config.ConfigService.ConfigKey;
import org.lgiavedoni.android.franq.service.event.EventService;
import org.lgiavedoni.android.franq.service.log.Logger;
import org.lgiavedoni.android.franq.util.date.DateUtil;
import org.lgiavedoni.android.franq.util.phone.AccountUtil;
import org.lgiavedoni.android.franq.util.thread.ThreadUtil;

public class CommunicationJabber extends Service implements ConnectionListener, PacketListener, CommunicationSource {

	public static final CommunicationJabber inst = new CommunicationJabber();
	private static XMPPConnection connection;
	private String host;
	private String port;
	private String service;
	private String username;
	private String password;
	private String endUserMail;
	private boolean endUserAvailable;
	private long lastContact;

	private final PacketFilter filter = new MessageTypeFilter(Message.Type.chat);
	private final ConfigurationListener configurationListener = new ConfigurationListener();

	private CommunicationJabber() {

	}

	private void connect() {
		try {
			Logger.inst.info("CommunicationJabber connecting...");
			// was connected before?
			if (connection != null) {
				connection.removeConnectionListener(this);
				connection.removePacketListener(this);
				connection.disconnect();
			}
			// Create a connection
			ConnectionConfiguration connConfig = new ConnectionConfiguration(host, Integer.parseInt(port), service);
			connection = new XMPPConnection(connConfig);

			connection.connect();
			// Log.i("XMPPClient", "[SettingsDialog] Connected to " +
			// connection.getHost());

			connection.login(username, password);
			// Log.i("XMPPClient", "Logged in as " + connection.getUser());

			// Set the status to available
			Presence presence = new Presence(Presence.Type.available);
			connection.sendPacket(presence);
			// xmppClient.setConnection(connection);
			connection.addPacketListener(this, filter);
			connection.addPacketListener(new PacketListener() {
				@Override
				public void processPacket(Packet paramPacket) {
				}
			}, new PacketFilter() {
				@Override
				public boolean accept(Packet p) {
					if (p instanceof Presence && p.getFrom().toLowerCase().trim().startsWith(endUserMail)) {
						endUserAvailable = ((Presence) p).isAvailable();
					}
					return false;
				}
			});

			connection.addConnectionListener(inst);

		} catch (XMPPException ex) {
			// Logger.inst.error(ex);
			Logger.inst.warning("Jabber login faild.");
		} catch (Throwable e) {
			Logger.inst.warning(e);
		}
	}

	private boolean isEndUserAvailable() {
		return endUserAvailable || ((lastContact + DateUtil.getMillisecondsOfMins(5) > DateUtil.getTimeInMillis()));
	}

	public void sendMessage(MessageOut message) {
		if (!isStarted() || !isEndUserAvailable())
			return;

		for (int i = 0; i < 3; i++)
			if (connection != null && connection.isConnected()) {
				Message msg = new Message();
				msg.setType(Message.Type.chat);
				if (CommunicationService.inst.getChannel() != null
						&& !CommunicationService.inst.getChannel().getUser().equals(org.lgiavedoni.android.franq.core.message.Message.UNKNOW_USER)) {
					msg.setTo(CommunicationService.inst.getChannel().getUser());
				} else {
					msg.setTo(endUserMail);
				}
				msg.setBody(message.getText());
				connection.sendPacket(msg);
				return;
			} else {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
				}
			}
		Logger.inst.warning("The message was not delivered by connection problems");
	}

	// gtalk seems to refuse non-chat messages
	// messages without bodies seem to be caused by things like typing
	public void processMessage(Message message) {
		if (message.getType().equals(Message.Type.chat) && message.getBody() != null) {
			lastContact = DateUtil.getTimeInMillis();
			String user = message.getFrom();
			if (user.indexOf('/') != -1)
				user = message.getFrom().substring(0, message.getFrom().indexOf('/'));
			CommunicationService.inst.reciveMessage(MessageIn.create(this, message.getBody(), user));
		}
	}

	public void processPacket(Packet packet) {
		processMessage((Message) packet);
	}

	private void reconnect() {
		long time = 15000;
		while (connection == null || !connection.isConnected()) {
			connect();
			ThreadUtil.sleep(time);
			time += 5000;
		}
	}

	@Override
	public void _run() {
		// setName("CommunicationJabber connecting...");
		reconnect();
	}

	@Override
	public void connectionClosed() {
		Logger.inst.debug("CommunicationJabber.connectionClosed");
	}

	@Override
	public void connectionClosedOnError(Exception arg0) {
		Logger.inst.info("CommunicationJabber.connectionClosedOnError");
		reconnect();
	}

	@Override
	public void reconnectingIn(int arg0) {
		Logger.inst.info("CommunicationJabber.reconnectingIn");
		reconnect();
	}

	@Override
	public void reconnectionFailed(Exception arg0) {
		Logger.inst.info("CommunicationJabber.reconnectionFailed");
	}

	@Override
	public void reconnectionSuccessful() {
		Logger.inst.info("CommunicationJabber.reconnectionSuccessful");
	}

	@Override
	public String getServiceName() {
		return "chat";
	}

	@Override
	public void stopInternal() {
		if (connection != null)
			connection.disconnect();
		connection = null;
		EventService.inst.removeObserver(configurationListener);
	}

	@Override
	protected void startInternal() {
		host = ConfigService.inst.getValue(ConfigKey.JABBER_HOST);
		port = ConfigService.inst.getValue(ConfigKey.JABBER_PORT);
		service = ConfigService.inst.getValue(ConfigKey.JABBER_SERVICE);
		username = ConfigService.inst.getValue(ConfigKey.FRANK_USERNAME);
		if (username.contains("@")) {
			username = username.substring(0, username.indexOf("@"));
		}
		password = ConfigService.inst.getValue(ConfigKey.FRANK_PASSWORD);
		endUserMail = AccountUtil.getUserEmail();

		EventService.inst.addObserver(configurationListener);
	}

	public class ConfigurationListener extends EventListener {

		@Override
		public void exec(Event event) {
			ConfigurationChangeEvent e = ((ConfigurationChangeEvent) event);
			if (e.containsAny(ConfigKey.JABBER_HOST, ConfigKey.JABBER_PORT, ConfigKey.JABBER_SERVICE)) {
				if (isStarted())
					restart();
			}
		}

		@Override
		public Class getClassEvent() {
			return ConfigurationChangeEvent.class;
		}
	}
}
